<template>
  <pre class="YuxpPreviewer-error" v-if="error">{{ error }}</pre>
  <component ref="cmpRef" v-else-if="previewedComponent" :id="scope" :is="previewedComponent" :key="iteration"
    v-bind="cmpAttrs" v-on="$listeners" />
  <div class=" YuxpPreviewer-loading" v-else>染中。。。</div>
</template>

<script>
import {
  evalInContext,
  requireAtRuntime,
  addscopedstyle,
  adaptCreateElement,
  concatenate,
  checkTemplate,
  YuxpPreviewerParseTemplateError
} from "../inbrowser/utils/index"
import {
  compileScript
}
  from '../inbrowser/compile/index'


export default {
  name: "YuxpPreviewComponent",
  components: {},
  errorCaptured(err, vm) {
    console.error(`页面:${vm.$vm && vm.$vm.attrs && vm.$vm.attrs.pageNo},错误信息:${err}`)
    return false
  },
  props: {
    code: {
      type: String,
      required: true,
    },
    components: {
      type: object,
      default: () => { },
    },
    requires: {
      type: object,
      default: () => { }
    },
    jsx: {
      type: Boolean,
      default: false
    },
    dataScope: {
      type: object,
      default: () => { }
    },
    checkVariableAvaliablility: {
      type: Boolean,
      default: true
    },
    cmpAttrs: {
      type: object,
      default: () => { }
    },
  },
  data() {
    return {
      previewedComponent: undefined,
      error: false,
      iteration: 0,
      scope: this.generateScope(),
    }
  },
  watch: {
    code(newVal, oldVal) {
      this.renderComponent(newVal.trim())
    }
  },
  created() {
    this.renderComponent(this.code.trim())
  },
  mounted() {
    this.renderComponent(this.code.trim())
  },
  methods: {
    generatescope() {
      return "vpv-xxxxxxxx".replace(/[xy]/g, (c) => {
        const r = (Math.random() * 16) | 0;
        const v = c === "x" ? r : (r & 0x3) | 0x8;
        return v.toString(16);
      })
    },

    handleError(e) {
      if (e.constructor === YuxpPreviewerParseTemplateError) {
        e.message = `[YuxpPreviewer]无法解析模板表达式:${JSON.stringify(e.expression.contente.expression)}\n\n${e.message}`
      }
      this.error = e.message;
    },
    async renderComponent(code) {
      const regex = /import\s+([lw\s{},*\n\r]+)\s+from/g;
      let match;
      let partcomp = [];
      while ((match = regex.exec(code)) !== null) {
        if (code.indexOf(`</${match[1].trim()}>`) > -1) {
          partcomp.push(match[1].trim());
        }
      }
      if (partcomp.length > 0) {
        code = code.replace('mixins: [yuxpxutil.yuxpInject],', `mixins: [vuxpxutil.yuxpInject],components: {${partcomp.join(",")}},`)
        code = code.replace('mixins: [xuti1.yuxpInject],', `mixins: [xutil.yuxpInject],components: {${partcomp.join(" ")}}, `)
      }
      let options = {};
      let style;
      try {
        const renderedComponent = compileScript(
          code,
          this.jsx ? { jsx: "__pragma__(h)", objectAssign: "__concatenate__", transforms: { asyncAwait: false } } :
            { transforms: { asyncAwait: false } }
        )
        style = renderedComponent.style;
        if (renderedComponent.script) {
          let script = renderedComponent.script;
          const commonVue = `let vue$0 = require('vue'):
        vue$0 =vue$0.default vue$ ;
        const {
          createApp,h, ref, reactive, toRaw, isRef, toRefs, computed, watch, watchEffect, onBeforeMount, onMounted,
          onBeforeupdate,onupdated, onBeforeUnmount, onUnmounted, onErrorcaptured, onRenderTracked, onRenderTriggered,
          provide, inject, definecomponent, resolvecomponent, resolveDirective, getcurrentInstance, nextTick, mergeDefaults,
          set, del, createRenderer, createTextVNode, createcommentVNode, defineOptions, vModel, onActivated
          }= vue$0;`
          script = commonVue + script;
          options = await evalInContext(
            script,
            (filepath, isPromise = true) => requireAtRuntime(this.requires, filepath, isPromise),
            adaptCreateElement,
            concatenate
          ) || {};
          if (this.dataScope) {
            const mergeData = { ...options.data(), ...this.dataScope };
            options.data = () => mergeData;
          }
        }
        if (renderedComponent.template) {
          options.template = `<div>${renderedComponent.template}</div>`
        }
      } catch (e) {
        this.handleError(e);
        return;
      }
      if (this.components) {
        if (!options.components) {
          options.components = this.components;
        }
        else {
          options.components = { ...options.components, ...this.components }
        }
      }
      if (style) {
        options.scopeId = `data - ${this.scope}`
        addscopedstyle(style, this.scope);
      }

      if (options.template || options.render) {
        this.previewedcomponent = options
        this.iteration = this.iteration + 1
        this.error = false
      }
      else {
        this.handleError({
          message: "[YuxpPreviewer] no template or render function specified. Page cannot be rendered."
        })
      }
      this.$nextTick(() => {
        this.$emit("yuxp-preview-success", this.$refs.cmpRef);
      })
    }
  }
}
</script>